﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using K9Nano.Remoting;
using K9Nano.Share.Extensions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Debugging;

namespace K9Nano.AspNetCore.Startup
{
    public class DefaultHostBuilder
    {
        public static IHostBuilder CreateHostBuilder<TStartup>(string[] args) where TStartup : class
        {
            return Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((hostContext, builder) =>
                {
                    var path = Path.Combine(Directory.GetCurrentDirectory(), "config");
                    builder.SetBasePath(path)
                        .AddJsonFile("appsettings.json", false, true)
                        .AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true, true)
                        .AddCommandLine(args)
                        .AddEnvironmentVariables();

                })
                .UseSerilog((hostingContext, loggerConfiguration) =>
                {
                    if (hostingContext.HostingEnvironment.IsDevelopment())
                    {
                      //  SelfLog.Enable(Console.WriteLine);
                    }

                    // No DI here, so get option from Environment temporary.
                    // see: https://github.com/serilog/serilog-aspnetcore/issues/169
                    loggerConfiguration
                        .ReadFrom.Configuration(hostingContext.Configuration)
                        .Enrich.FromLogContext();
                })
                .ConfigureWebHostDefaults(webHostBuilder =>
                {
                    webHostBuilder
                        .ConfigureKestrel((hostContext, options) =>
                        {
                            options.Limits.MaxRequestBodySize = 20 * 1024L * 1024L;

                            // Http
                            var httpPort = hostContext.Configuration.GetSection("ApiInfo:HttpPort").Get<int>();
                            if (httpPort > 0)
                            {
                                options.Listen(IPAddress.Parse("0.0.0.0"), httpPort, o => o.Protocols = HttpProtocols.Http1AndHttp2);
                            }

                            // GRPC
                            var grpcPort = hostContext.Configuration.GetSection("ApiInfo:GrpcPort").Get<int>();
                            if (grpcPort > 0)
                            {
                                options.Listen(IPAddress.Parse("0.0.0.0"), grpcPort, o => o.Protocols = HttpProtocols.Http2);
                            }
                        })
                        .UseStartup<TStartup>();
                });
        }

        public static async Task RunAsync(IHostBuilder builder)
        {
            try
            {
                var host = builder
                    .Build();

                var apiInfo = host.Services.GetRequiredService<IApiInfo>();
                var logger = host.Services.GetRequiredService<ILoggerFactory>().CreateLogger("Startup");
                logger.LogInformation($"App started: {apiInfo}");

                await host
                    .RunAsync();
            }
            catch (Exception ex)
            {
                var error = ex.GetDisplayMessage();

                Console.WriteLine("App crashed");
                Console.WriteLine(error);

                StreamWriter writer = null;
                try
                {
                    var path = Directory.CreateDirectory("log");
                    var crashLog = Path.Combine(path.FullName, "crash.log");
                    writer = new StreamWriter(crashLog, true, Encoding.UTF8);
                    writer.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} App crashed");
                    writer.WriteLine(error);
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Save cash log failed: {e.Message}");
                }
                finally
                {
                    writer?.Dispose();
                }
            }
        }
    }
}